/* * Sun Public License Notice * * The contents of this file are subject to the Sun Public License * Version 1.0 (the "License"). You may not use this file except in * compliance with the License. A copy of the License is available at * http://www.sun.com/ * * The Original Code is Forte for Java, Community Edition. The Initial * Developer of the Original Code is Sun Microsystems, Inc. Portions * Copyright 1997-2000 Sun Microsystems, Inc. All Rights Reserved. */ package org.openide.awt; import java.util.StringTokenizer; import java.awt.Component; import java.awt.Dimension; import java.awt.Frame; import java.awt.Point; import java.awt.Rectangle; import java.awt.Toolkit; import java.awt.Window; import javax.swing.JFrame; import javax.swing.JMenu; import javax.swing.JPopupMenu; import javax.swing.SwingUtilities; //TODO: Evan: OutputTab is imported to get the TYPICAL_WINDOWS_TASKBAR_HEIGHT //TODO: constant. The constant should be kept in a more generic place //TODO: such as org.openide.util.Utilities. import org.netbeans.core.output.OutputTab; import org.openide.util.Utilities; /** A class that contains a set of utility classes and methods * around displaying and positioning popup menus. * * Popup menus sometimes need to be repositioned so that they * don't "fall off" the edges of the screen. * * Some of the menus have items that are added dynamically, that is, * after the menu is displayed. These menus are often placed correctly * for their initial size, but will need to be repositioned as they * grow. * * @author Evan Adams */ public class JPopupMenuUtils extends Object { private static Rectangle screenRect; /* * Called when a visible menu has dynamically changed. Ensure that * it stays on the screen. Compute its new location and, * if it differs from the current one, move the popup. * * @param popup the popup menu */ public static void dynamicChange(JPopupMenu popup, boolean usedToBeContained) { if (!popup.isVisible()) { return; } Point p = popup.getLocationOnScreen(); Point newPt = getPopupMenuOrigin(popup, p); boolean willBeContained = willPopupBeContained(popup, newPt); if (usedToBeContained != willBeContained) { popup.setVisible(false); } if (!newPt.equals(p)) { popup.setLocation(newPt.x, newPt.y); } if (usedToBeContained != willBeContained) { popup.setVisible(true); } } /* * Called when a visible submenu (pullright) has dynamically changed. * Ensure that it stays on the screen. If it doesn't fit, then hide * the popup and redisplay it. This causes JMenu's placement code * to get executed again which may change the submens to go up rather * than down. * * @param popup the popup menu */ public static void dynamicChangeToSubmenu(JPopupMenu popup, boolean usedToBeContained) { Object invoker = popup.getInvoker(); if (!(invoker instanceof JMenu)) { return; } JMenu menu = (JMenu) invoker; if (!popup.isVisible()) { return; } Point p = popup.getLocationOnScreen(); Dimension popupSize = popup.getPreferredSize (); Rectangle popupRect = new Rectangle(p, popupSize); Rectangle screenRect = getScreenRect(); boolean willBeContained = isPopupContained(popup); if (!screenRect.contains(popupRect)) { /* * The menu grew off the edge of the screen. */ menu.setPopupMenuVisible(false); menu.setPopupMenuVisible(true); } else if (usedToBeContained != willBeContained) { /* * The menu grew off the edge of the containing window. * Use the setVisible() hack to change the menu from * lightweight to heavyweight. */ popup.setVisible(false); popup.setVisible(true); } } /* * Return the point for the origin of this popup. * This is where the adjustments are made to ensure the * popup stays on the screen. * * @param popup the popup menu * @param p the popup menu's origin * @return the popup menu's new origin */ static Point getPopupMenuOrigin(JPopupMenu popup, Point p) { Point newPt = new Point(p); Dimension popupSize = popup.getPreferredSize (); Rectangle screenRect = getScreenRect(); int popupRight = newPt.x + popupSize.width; int popupBottom = newPt.y + popupSize.height; int screenRight = screenRect.x + screenRect.width; int screenBottom = screenRect.y + screenRect.height; if (popupRight > screenRight) { // Are we off the right edge? newPt.x = screenRight - popupSize.width; } if (newPt.x < screenRect.x) { // Are we off the left edge? newPt.x = screenRect.x; } if (popupBottom > screenBottom) { // Are we off the bottom edge? newPt.y = screenBottom - popupSize.height; } if (newPt.y < screenRect.y) { // Are we off the top edge? newPt.y = screenRect.y; } return newPt; } /* * Return whether or not the given popup is contained by its * parent window. Uses the current location and size of the popup. * * @return boolean indicating if the popup is contained */ public static boolean isPopupContained(JPopupMenu popup) { if (!popup.isVisible()) { return false; } return willPopupBeContained(popup, popup.getLocationOnScreen()); } /* * Return whether or not the given popup will be contained by * its parent window if it is moved to <code>origin</origin>. * Use its current size. * * @param <code>popup</code> the popup to be tested * @param <code>origin</code> location of the popup to be tested * @return boolean indicating if the popup will be contained */ private static boolean willPopupBeContained(JPopupMenu popup, Point origin) { if (!popup.isVisible()) { return false; } Window w = SwingUtilities.windowForComponent (popup.getInvoker()); Rectangle r = new Rectangle (origin, popup.getSize ()); return w != null && w.getBounds ().contains (r); } /* * Return a rectange defining the usable portion of the screen. * Designed to provide a way to account for the taskbar in Windows. * Ultimately, we should make a native call when running on Windows * to determine screen rectangle. For now we assume the taskbar is * at the bottom and that it is just one row tall. * * @return a rectangle defining the usable area. */ public static Rectangle getScreenRect() { if (screenRect != null) { return screenRect; } screenRect = getRectFromProperty(); if (screenRect != null) { return screenRect; } Dimension screenSize = Toolkit.getDefaultToolkit ().getScreenSize (); if (Utilities.isWindows()) { screenSize.height -= OutputTab.TYPICAL_WINDOWS_TASKBAR_HEIGHT; } screenRect = new Rectangle(0, 0, screenSize.width, screenSize.height); return screenRect; } /* * If the property "netbeans.screen.rect" contains the description * of a rectangle, then return that rectangle. Used to allow the * user to specify the usable portion of the screen. Intended to * describe where and how big the taskbar is on Windows. */ private static Rectangle getRectFromProperty() { String prop = System.getProperty("netbeans.screen.rect"); if (prop == null) { return null; } StringTokenizer strtok = new StringTokenizer(prop, ",", false); if (strtok.countTokens() != 4) { return null; } try { int x = Integer.parseInt(strtok.nextToken()); int y = Integer.parseInt(strtok.nextToken()); int width = Integer.parseInt(strtok.nextToken()); int height = Integer.parseInt(strtok.nextToken()); return new Rectangle(x, y, width, height); } catch (NumberFormatException e) { return null; } } }